package com.kandinfo.base.common.xmemcached;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeoutException;

import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.exception.MemcachedException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;

public class SimpleMemcachedCache implements Cache {
    private static final Logger LOGGER = LoggerFactory
            .getLogger(SimpleMemcachedCache.class);

    public final static Set<String> LOCAL_KEYS =   new HashSet<String>();
    private static final String CACHE_SET_KEY = "__cacheKey_By_Cache_Name";

    private MemcachedClient memcachedClient;
    private int expiration;

    private String cacheName;
    private String namespace;

    public SimpleMemcachedCache(String name, String namespace) {
        this.cacheName = name;
        this.namespace = namespace;
    }

    @Override
    public String getName() {
        return cacheName;
    }

    @Override
    public Object getNativeCache() {
        return memcachedClient;
    }

    private String getKey(final Object key) {
        return (this.namespace + "_" + this.getName() + "_" + key.toString()).replaceAll("\\p{Cntrl}]|\\p{Space}", "_");
    }

    private void logException(TimeoutException e) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.error(e.getMessage(), e);
        } else {
            LOGGER.warn(e.getMessage(), e);
        }
    }

    private void logException(InterruptedException e) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.error(e.getMessage(), e);
        } else {
            LOGGER.warn(e.getMessage(), e);
        }
    }

    private void logException(MemcachedException e) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.error(e.getMessage(), e);
        } else {
            LOGGER.warn(e.getMessage(), e);
        }
    }

    private Object getValue(Object key) {
        String keyStr = getKey(key);
        Object value = null;
        long start = System.currentTimeMillis();
        try {
            value = memcachedClient.get(keyStr);

        } catch (InterruptedException e) {
            logException(e);
        } catch (TimeoutException e) {
            logException(e);
        } catch (MemcachedException e) {
            logException(e);
        } finally {
            long end = System.currentTimeMillis();
            if (end - start > 1000) {
                LOGGER.warn(" get cache slow key {} cache {} ", keyStr, cacheName);
            }
        }
        return value;
    }

    @Override
    public ValueWrapper get(Object key) {
        Object value = getValue(key);
        if (value == null) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.warn("Can not get cache with key {} from cache {}", key,
                        cacheName);
            }
            return null;
        } else {
            LOGGER.trace("Get cache value with key {} from cache {}.", key,
                    cacheName);

        }

        return new SimpleValueWrapper(value);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(Object key, Class<T> type) {
        return (T) getValue(key);
    }

    @Override
    public void put(Object key, Object value) {
        if (key != null && value != null) {
            try {
                String keyStr = getKey(key);
                memcachedClient.set(keyStr, expiration, value);
                putLocal(keyStr);
                if(LOGGER.isTraceEnabled()){
                	LOGGER.trace("Put value into cache {} with real key {}.", cacheName,
                        keyStr, this.expiration);
                }
            } catch (TimeoutException e) {
                logException(e);
            } catch (InterruptedException e) {
                logException(e);
            } catch (MemcachedException e) {
                logException(e);
            }
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.warn(
                        "Cannot put value '{}' to cache {} because key {} or value is null",
                        key, value, cacheName);
            }
        }
    }

    
    
    private void putLocal(String key) {
    	if(LOCAL_KEYS.contains(key)){
    		return;
    	}
    	LOCAL_KEYS.add(key);
        Set<String> keys = null;
        try {
            keys = memcachedClient.get(CACHE_SET_KEY + cacheName);
        } catch (TimeoutException e) {
            logException(e);
        } catch (InterruptedException e) {
            logException(e);
        } catch (MemcachedException e) {
            logException(e);
        }
		try {
			if (null == keys) {
				keys = new HashSet<String>();
			}
			keys.add(key);
			memcachedClient.set(CACHE_SET_KEY + cacheName, 0, keys);
		} catch (TimeoutException e) {
			logException(e);
		} catch (InterruptedException e) {
			logException(e);
		} catch (MemcachedException e) {
			logException(e);
		}
    }

    @Override
    public void evict(Object key) {
        if (key != null) {
            try {
                String keyStr = getKey(key);
                memcachedClient.delete(keyStr);
                if(LOGGER.isDebugEnabled()){
                	LOGGER.debug("Evict {} from cache {}", keyStr, cacheName);
                }
            } catch (TimeoutException e) {
                logException(e);
            } catch (InterruptedException e) {
                logException(e);
            } catch (MemcachedException e) {
                logException(e);
            }
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.warn("Cannot evict from cache {} because key is null",
                        cacheName);
            }
        }
    }

    @Override
    public void clear() {

        Set<String> keys = null;
        try {
            keys = memcachedClient.get(CACHE_SET_KEY + cacheName);

        } catch (TimeoutException e) {
            logException(e);
        } catch (InterruptedException e) {
            logException(e);
        } catch (MemcachedException e) {
            logException(e);
        }
        if (null != keys) {
            for (String key : keys) {
                try {
                    memcachedClient.delete(key);
                    if(LOGGER.isDebugEnabled()){
                    	LOGGER.debug("clear {} from cache {}", key, cacheName);
                    }
                } catch (TimeoutException e) {
                    logException(e);
                } catch (InterruptedException e) {
                    logException(e);
                } catch (MemcachedException e) {
                    logException(e);
                }
            }
        }
    }

    public MemcachedClient getMemcachedClient() {
        return memcachedClient;
    }

    public void setMemcachedClient(MemcachedClient memcachedClient) {
        this.memcachedClient = memcachedClient;
    }

    public int getExpiration() {
        return expiration;
    }

    public void setExpiration(int expiration) {
        this.expiration = expiration;
    }

    @Override
    public ValueWrapper putIfAbsent(Object arg0, Object arg1) {
        return null;
    }

}
